const std = @import("std");
const net = std.net;
const posix = std.posix;
const http = std.http;
const builtin = @import("builtin");

const ssdp = @import("ssdp.zig");
const renderer = @import("renderer.zig");
const utils = @import("utils.zig");
const uuid = @import("uuid");

const Listenner = ssdp.listenner(Device);

pub const Device = struct {
    alloctor: std.mem.Allocator = undefined,
    listenner: Listenner = undefined,
    name: [*]const u8 = "UPnP Renderer",
    ip_addr: net.Address = undefined,
    event: std.Thread.ResetEvent = .{},
    advertisement_duration: i32 = 100,
    uuid_str: uuid.urn.Urn = undefined,

    pub fn init(self: *Device, alloctor: std.mem.Allocator, ip: []const u8) !void {
        self.ip_addr = try net.Address.parseIp(ip, 8080);
        self.alloctor = alloctor;
        const id = uuid.v4.new();
        self.uuid_str = uuid.urn.serialize(id);
        self.listenner = Listenner{ .alloctor = self.alloctor, .device = self };
        try self.listenner.init();
        try self.listenner.start();
    }

    pub fn deinit(self: *Device) void {
        self.listenner.stop();
    }

    pub fn onMSEARCH(self: *Device, writer: anytype, head: ssdp.MSearchHead) !void {
        std.log.debug("{s} repsond host: {any}", .{ @typeName(Device), head.host });

        if (contains(u8, head.st, ":schemas-upnp-org:device:MediaRenderer:")) {
            try Templ.alive(
                writer,
                self.advertisement_duration,
                self.ip_addr,
                head.st,
                &self.uuid_str,
            );
        }
    }

    pub fn wait(self: *Device) void {
        self.event.wait();
    }
};

inline fn contains(comptime T: type, haystack: []const T, needle: []const T) bool {
    return if (std.mem.indexOf(T, haystack, needle)) |_| true else false;
}

pub const Server = struct {
    alloctor: std.mem.Allocator,
    server: net.Server,
    addr: net.Address,
    device: *Device,

    pub fn init(alloctor: std.mem.Allocator, ip: []const u8, device: *Device) !Server {
        const address = try net.Address.parseIp("0.0.0.0", 8080);
        return .{
            .alloctor = alloctor,
            .server = try address.listen(.{ .reuse_address = true }),
            .addr = try net.Address.parseIp(ip, 8080),
            .device = device,
        };
    }

    pub fn deinit(self: *Server) void {
        self.server.deinit();
    }

    pub fn serve(self: *Server) void {
        var buf: [1024]u8 = undefined;

        std.log.debug("upnp server started", .{});
        while (true) {
            const conn = self.server.accept() catch |e| {
                std.log.warn("{s}: faile to accept: {any}", .{ @typeName(@TypeOf(self.server)), e });
                continue;
            };

            var http_server = std.http.Server.init(conn, &buf);
            const req = http_server.receiveHead() catch |e| {
                std.log.warn("{s}: faile to accept: {any}", .{ @typeName(@TypeOf(http_server)), e });
                continue;
            };

            std.log.debug("upnp server: accepted {any}: {s}", .{ req.head.method, req.head.target });
            const ret = switch (req.head.method) {
                .GET => self.doGET(req),
                else => error.UnknownMethod,
            };

            if (ret) |_| {} else |err| {
                std.log.err("HTTP repsond error: {any}", .{err});
            }

            break;
        }

        std.log.debug("upnp server stopped", .{});
    }

    fn doGET(self: *Server, req: std.http.Server.Request) !void {
        _ = self;
        if (std.mem.eql(u8, req.head.target, "/dlna/desc.xml")) {
            std.log.debug("respond GET /dlna/desc.xml", .{});
        }
    }
};

pub const Templ = struct {
    pub fn alive(writer: anytype, avd_age: i32, server_addr: net.Address, st: []const u8, uuid_str: []const u8) !void {
        const fmt =
            "HTTP/1.1 200 OK\r\n" ++
            "CACHE-CONTROL: max-age={d}\r\n" ++
            "EXT:\r\n" ++
            "DATE: {rfc1123}\r\n" ++
            "LOCATION: http://{any}/dlna/desc.xml\r\n" ++
            "SERVER: Linux/6.18.12 UPnP/2.0 zumr/0.0.1\r\n" ++
            "ST: {s}\r\n" ++
            "USN: uuid:{s}\r\n\r\n";
        try writer.print(fmt, .{
            avd_age,
            utils.Datetime.now(),
            server_addr,
            st,
            uuid_str,
        });
    }
};
